﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.Options;
using Tsual.DataPipeline;
using Tsual.DataPipeline.AspCore;
using Tsual.DataPipeline.Redis;
using Tsual.DataPipeline.Http;
using System.Net.Http;
using System.Net;
using Newtonsoft.Json;
using Tsual.Wechat.Message.Impl;
using System.Threading.Tasks;
using Tsual.Wechat.Cache;
using RedLockNet.SERedis;
using StackExchange.Redis;

namespace Tsual.Wechat.Pipes
{
    public class DpmmKnownUser : IConvertible<KeyValuePair<string, string>>, IConvertible<LockType>
    {
        public string openid { get; set; }

        public DpmmKnownUser(string openid)
        {
            if (string.IsNullOrWhiteSpace(openid))
            {
                throw new ArgumentNullException(nameof(openid));
            }

            this.openid = openid;
        }

        KeyValuePair<string, string> IConvertible<KeyValuePair<string, string>>.Convert() => new KeyValuePair<string, string>(WechatCacheKeyEnum.USER_KNOWN.ToRedisKey(), openid);

        LockType IConvertible<LockType>.Convert() => LockType.Normal;
    }

    public class DpmmAccessToken : IConvertible<string>, IConvertible<HttpRequestMessage>, IConvertible<Func<HttpResponseMessage, Task<ExpirablePipeValue<string>>>>, IConvertible<LockType>
    {
        public LockType Convert() => LockType.Lock;

        private DpmmAccessToken()
        {

        }

        public static DpmmAccessToken Instance { get; } = new DpmmAccessToken();

        string IConvertible<string>.Convert() => WechatCacheKeyEnum.ACCESS_TOKEN.ToRedisKey();

        HttpRequestMessage IConvertible<HttpRequestMessage>.Convert() => new HttpRequestMessage(HttpMethod.Get, $"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={WebUtility.UrlEncode(WechatContext.Instance.AppId)}&secret={WebUtility.UrlEncode(WechatContext.Instance.AppSecret)}");

        Func<HttpResponseMessage, Task<ExpirablePipeValue<string>>> IConvertible<Func<HttpResponseMessage, Task<ExpirablePipeValue<string>>>>.Convert() => async response =>
        {
            var rtn = JsonConvert.DeserializeObject<WsrmAccessToken>(await response.Content.ReadAsStringAsync());
            return new ExpirablePipeValue<string>()
            {
                Value = rtn.access_token,
                AbsoluteExpireTime = DateTimeOffset.Now.AddSeconds(rtn.expires_in - 30 > 0 ? rtn.expires_in - 30 : rtn.expires_in)
            };
        };
    }

    public class AbstractWechatServerDpmm<T> : IConvertible<Func<HttpResponseMessage, Task<T>>>
        where T : BasicWechatServerResponseModel
    {
        public Func<HttpResponseMessage, Task<T>> Convert() => response => BasicWechatServerResponseModel.PackageServerReturnFromHttpResponseAsync<T>(response);
    }

    public class DpmmQueryTemplateId : AbstractWechatServerDpmm<WsrmTemplateId>, IConvertible<HttpRequestMessage>
    {
        public string shortid { get; set; }

        public DpmmQueryTemplateId(string shortid)
        {
            this.shortid = shortid ?? throw new ArgumentNullException(nameof(shortid));
        }


        HttpRequestMessage IConvertible<HttpRequestMessage>.Convert()
        {
            var request = new HttpRequestMessage(HttpMethod.Post, $"https://api.weixin.qq.com/cgi-bin/template/api_add_template?access_token={WechatContext.Instance.GetAccessToken()}");
            request.Content = new StringContent(JsonConvert.SerializeObject(new Dictionary<string, string>() { { "template_id_short", shortid } }), Encoding.UTF8, @"application/json");
            return request;
        }
    }

    public class DpmmSendTemplate : IConvertible<HttpRequestMessage, WssmTemplateSend>
    {
        private DpmmSendTemplate() { }

        public static DpmmSendTemplate Instance { get; } = new DpmmSendTemplate();

        public HttpRequestMessage Convert(WssmTemplateSend condition)
        {
            var request = new HttpRequestMessage(HttpMethod.Post, $"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={WebUtility.UrlEncode(WechatContext.Instance.GetAccessToken())}");
            request.Content = new StringContent(JsonConvert.SerializeObject(condition), Encoding.UTF8, @"application/json");
            return request;
        }
    }

    public class DpmmUserAccessToken : AbstractWechatServerDpmm<WsrmUserAccessToken>, IConvertible<HttpRequestMessage>, IConvertible<KeyValuePair<string, string>>, IConvertible<LockType>
    {
        public string PageCode { get; set; }
        public string Openid { get; set; }

        public DpmmUserAccessToken() { }

        public DpmmUserAccessToken(string pageCode, string openid)
        {
            PageCode = pageCode;
            Openid = openid;
        }

        KeyValuePair<string, string> IConvertible<KeyValuePair<string, string>>.Convert()
        {
            return new KeyValuePair<string, string>(WechatCacheKeyEnum.USER_ACCESS_TOKEN.ToRedisKey(), Openid);
        }

        LockType IConvertible<LockType>.Convert()
        {
            return LockType.Lock;
        }

        HttpRequestMessage IConvertible<HttpRequestMessage>.Convert()
        {
            if (string.IsNullOrEmpty(PageCode)) return null;
            return new HttpRequestMessage(HttpMethod.Get, $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={WebUtility.UrlEncode(WechatContext.Instance.AppId)}&secret={WebUtility.UrlEncode(WechatContext.Instance.AppSecret)}&code={WebUtility.UrlEncode(PageCode)}&grant_type=authorization_code");
        }
    }

    public class DpmmUserAccessTokenPageCache : IConvertible<KeyValuePair<string, string>>, IConvertible<LockType>
    {
        public DpmmUserAccessToken origin { get; set; }

        public KeyValuePair<string, string> Convert()
        {
            return new KeyValuePair<string, string>(WechatCacheKeyEnum.USER_ACCESS_PAGE_CODE.ToRedisKey(), origin.PageCode);
        }

        LockType IConvertible<LockType>.Convert()
        {
            return LockType.Lock;
        }
    }



    public class DpmmUserRefreshToken : IConvertible<KeyValuePair<string, string>>, IConvertible<LockType>
    {
        public string openid { get; set; }
        public DateTimeOffset? expiry { get; set; }

        LockType IConvertible<LockType>.Convert() => LockType.Lock;

        KeyValuePair<string, string> IConvertible<KeyValuePair<string, string>>.Convert()
        {
            return new KeyValuePair<string, string>(WechatCacheKeyEnum.USER_REFRESH_TOKEN.ToRedisKey(), openid);
        }
    }

    public class DpmmUserRefreshTokenInner : AbstractWechatServerDpmm<WsrmUserAccessToken>, IConvertible<HttpRequestMessage>, IConvertible<KeyValuePair<string, string>>, IConvertible<LockType>
    {
        public string refresh_token { get; set; }
        public DateTimeOffset? expiry { get; set; }

        LockType IConvertible<LockType>.Convert() => LockType.Lock;

        KeyValuePair<string, string> IConvertible<KeyValuePair<string, string>>.Convert()
        {
            return new KeyValuePair<string, string>(WechatCacheKeyEnum.USER_REFRESH_TOKEN.ToRedisKey(), refresh_token);
        }

        

        HttpRequestMessage IConvertible<HttpRequestMessage>.Convert()
        {
            return new HttpRequestMessage(HttpMethod.Get, $"https://api.weixin.qq.com/sns/oauth2/refresh_token?appid={WebUtility.UrlEncode(WechatContext.Instance.AppId)}&grant_type=refresh_token&refresh_token={WebUtility.UrlEncode(refresh_token)}");
        }
    }

    public class DpmcUserRefreshToken : IConvertor<WsrmUserAccessToken, ExpirablePipeValue<string>>, IConvertor<DpmmUserRefreshTokenInner, ExpirablePipeValue<string>>
    {
        public WsrmUserAccessToken Convert(ExpirablePipeValue<string> obj)
        {
            return new WsrmUserAccessToken()
            {
                refresh_token = obj.Value
            };
        }

        public ExpirablePipeValue<string> Convert(WsrmUserAccessToken obj)
        {
            return new ExpirablePipeValue<string>()
            {
                AbsoluteExpireTime = DateTimeOffset.Now.AddDays(20),
                Value = obj.refresh_token
            };
        }

        public ExpirablePipeValue<string> Convert(DpmmUserRefreshTokenInner obj)
        {
            return new ExpirablePipeValue<string>()
            {
                Value = obj.refresh_token
            };
        }

        DpmmUserRefreshTokenInner IConvertor<DpmmUserRefreshTokenInner, ExpirablePipeValue<string>>.Convert(ExpirablePipeValue<string> obj)
        {
            throw new NotImplementedException();
        }
    }



    public class DpmcUserPageCode : IConvertor<WsrmUserAccessToken, ExpirablePipeValue<string>>, IHubConvertor<WsrmUserAccessToken, DpmmUserAccessToken, DpmmUserAccessTokenPageCache>
    {
        public ExpirablePipeValue<string> Convert(WsrmUserAccessToken obj)
        {
            return new ExpirablePipeValue<string>()
            {
                AbsoluteExpireTime = DateTimeOffset.Now.AddMinutes(30),
                Value = JsonConvert.SerializeObject(obj)
            };
        }

        public WsrmUserAccessToken Convert(ExpirablePipeValue<string> obj)
        {
            return JsonConvert.DeserializeObject<WsrmUserAccessToken>(obj.Value);
        }

        public DpmmUserAccessTokenPageCache Convert(WsrmUserAccessToken data, DpmmUserAccessToken meta)
        {
            return data == null ? new DpmmUserAccessTokenPageCache()
            {
                origin = meta
            } : new DpmmUserAccessTokenPageCache()
            {
                origin = new DpmmUserAccessToken()
                {
                    Openid = data.openid,
                    PageCode = meta.PageCode
                }
            };
        }
    }

    public class DpccRefreshTokenWrite : IConvertor<WsrmUserAccessToken, KeyValuePair<string, string>>,
        IConvertor<WsrmUserAccessToken, ExpirablePipeValue<string>>
    {
        public WsrmUserAccessToken Convert(KeyValuePair<string, string> obj)
        {
            throw new NotImplementedException();
        }

        public KeyValuePair<string, string> Convert(WsrmUserAccessToken obj)
        {
            return new KeyValuePair<string, string>(WechatCacheKeyEnum.USER_REFRESH_TOKEN.ToRedisKey(), obj.openid);
        }

        public WsrmUserAccessToken Convert(ExpirablePipeValue<string> obj)
        {
            throw new NotImplementedException();
        }

        ExpirablePipeValue<string> IConvertor<WsrmUserAccessToken, ExpirablePipeValue<string>>.Convert(WsrmUserAccessToken obj)
        {
            return new ExpirablePipeValue<string>()
            {
                Value = obj.refresh_token,
                AbsoluteExpireTime = DateTimeOffset.Now.AddDays(20)
            };
        }
    }

    public class DpmcUserAccessToken : IConvertor<DpmmUserAccessToken, DpmmUserRefreshToken>, IConvertor<WsrmUserAccessToken, ExpirablePipeValue<string>>, IConvertor<DpmmUserRefreshTokenInner, ExpirablePipeValue<string>>,IConvertor<DpmmUserAccessTokenPageCache,DpmmUserAccessToken>
    {
        DpmmUserAccessToken IConvertor<DpmmUserAccessToken, DpmmUserRefreshToken>.Convert(DpmmUserRefreshToken obj)
        {
            return new DpmmUserAccessToken(null, obj.openid);
        }

        DpmmUserRefreshToken IConvertor<DpmmUserAccessToken, DpmmUserRefreshToken>.Convert(DpmmUserAccessToken t0)
        {
            return new DpmmUserRefreshToken()
            {
                openid = t0.Openid
            };
        }

        WsrmUserAccessToken IConvertor<WsrmUserAccessToken, ExpirablePipeValue<string>>.Convert(ExpirablePipeValue<string> obj)
        {
            return new WsrmUserAccessToken()
            {
                access_token = obj.Value,
                expires_in = obj.AbsoluteExpireTime.HasValue ? System.Convert.ToInt32((obj.AbsoluteExpireTime.Value - DateTime.Now).TotalSeconds) : 0
            };
        }

        ExpirablePipeValue<string> IConvertor<WsrmUserAccessToken, ExpirablePipeValue<string>>.Convert(WsrmUserAccessToken t0)
        {
            return new ExpirablePipeValue<string>()
            {
                Value = t0.access_token,
                AbsoluteExpireTime = DateTimeOffset.Now.AddSeconds(t0.expires_in)
            };
        }

        DpmmUserRefreshTokenInner IConvertor<DpmmUserRefreshTokenInner, ExpirablePipeValue<string>>.Convert(ExpirablePipeValue<string> obj)
        {
            return new DpmmUserRefreshTokenInner()
            {
                expiry = obj.AbsoluteExpireTime,
                refresh_token = obj.Value
            };
        }

        ExpirablePipeValue<string> IConvertor<DpmmUserRefreshTokenInner, ExpirablePipeValue<string>>.Convert(DpmmUserRefreshTokenInner t0)
        {
            return new ExpirablePipeValue<string>()
            {
                AbsoluteExpireTime = t0.expiry,
                Value = t0.refresh_token
            };
        }

        DpmmUserAccessTokenPageCache IConvertor<DpmmUserAccessTokenPageCache, DpmmUserAccessToken>.Convert(DpmmUserAccessToken obj)
        {
            return new DpmmUserAccessTokenPageCache() { origin = obj };
        }

        DpmmUserAccessToken IConvertor<DpmmUserAccessTokenPageCache, DpmmUserAccessToken>.Convert(DpmmUserAccessTokenPageCache t0)
        {
            return t0.origin;
        }
    }

    public class DpmmUserAccessTokenRefresh : IConvertible<KeyValuePair<string, string>>, IConvertible<LockType>
    {
        public string openid { get; set; }

        public DpmmUserAccessTokenRefresh(string openid)
        {
            if (string.IsNullOrWhiteSpace(openid))
            {
                throw new ArgumentException("message", nameof(openid));
            }

            this.openid = openid;
        }

        public KeyValuePair<string, string> Convert() => new KeyValuePair<string, string>(WechatCacheKeyEnum.USER_REFRESH_TOKEN.ToRedisKey(), openid);

        LockType IConvertible<LockType>.Convert() => LockType.Lock;
    }
}
